一:该例子是笔者在实际项目应用过程中,针对项目完成的一套基于poi的导入导出例子,其中一些与项目有关的代码大家直接替换成自己的需求即可。
二:笔者在项目中使用的是poi的XSSF,对应maven的pom依赖如下:
3.9 org.apache.poi poi ${poi.version} org.apache.poi poi-ooxml ${poi.version} org.apache.poi poi-ooxml-schemas ${poi.version}
接下来首先是jsp页面:
其中导出涉及不到表单数据的提交,笔者为了操作简便,直接将查询的表单的action换成了导出的地址,在此并没有列出来,大家换成a标签也可。
然后是控制层代码:
导出:
/** *导出数据 *@version 2018-04-02 */ @RequiresPermissions("wxpt:workorder:wxptWorkorderInfo:view") @RequestMapping(value = "export", method = RequestMethod.POST) public String exportToFile(HttpServletRequest request, HttpServletResponse response, RedirectAttributes redirectAttributes, WxptWorkorderInfo wxptWorkorderInfo) { try { String fileName = DateUtils.getDate("工单yyyyMMddHHmmss") + ".xlsx"; Listlist = wxptWorkorderInfoService.findList(wxptWorkorderInfo); new ExportExcel("工单", WxptWorkorderInfo.class).setDataList(list).write(response, fileName).dispose(); return null; } catch (Exception e) { addMessage(redirectAttributes, "导出工单失败!失败信息:" + e.getMessage()); } return "redirect:" + Global.getAdminPath() + "/wxpt/workorder/wxptWorkorderInfo/?repage"; }
exportExcel:(一个工具类,直接复制粘贴即可,注释很明确)
/** * Copyright © 2012-2016 smkj All rights reserved. */package com.sm.modules.wxpt.utils.excel;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.OutputStream;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.util.Collections;import java.util.Comparator;import java.util.Date;import java.util.HashMap;import java.util.List;import java.util.Map;import javax.servlet.http.HttpServletResponse;import org.apache.commons.lang3.StringUtils;import org.apache.poi.ss.usermodel.Cell;import org.apache.poi.ss.usermodel.CellStyle;import org.apache.poi.ss.usermodel.Comment;import org.apache.poi.ss.usermodel.Font;import org.apache.poi.ss.usermodel.IndexedColors;import org.apache.poi.ss.usermodel.Row;import org.apache.poi.ss.usermodel.Sheet;import org.apache.poi.ss.usermodel.Workbook;import org.apache.poi.ss.util.CellRangeAddress;import org.apache.poi.xssf.streaming.SXSSFWorkbook;import org.apache.poi.xssf.usermodel.XSSFClientAnchor;import org.apache.poi.xssf.usermodel.XSSFRichTextString;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import com.google.common.collect.Lists;import com.sm.modules.sys.utils.DictUtils;import com.sm.utils.Encodes;import com.sm.utils.Reflections;import com.sm.utils.excel.annotation.ExcelField;/** * 导出Excel文件(导出“XLSX”格式,支持大数据量导出 @see org.apache.poi.ss.SpreadsheetVersion) * @author 冯文哲 * @version 2013-04-21 */public class ExportExcel { private static Logger log = LoggerFactory.getLogger(ExportExcel.class); /** * 工作薄对象 */ private SXSSFWorkbook wb; /** * 工作表对象 */ private Sheet sheet; /** * 样式列表 */ private Mapstyles; /** * 当前行号 */ private int rownum; /** * 注解列表(Object[]{ ExcelField, Field/Method }) */ List
该导出是基于bean的导出,就要用到java的反射机制,关于bean的配置是在java实体类上通过注解来实现的:
/** * Copyright © 2012-2016 smkj All rights reserved. */package com.sm.modules.wxpt.workorder.entity;import java.util.Date;import javax.validation.constraints.NotNull;import org.hibernate.validator.constraints.Length;import org.hibernate.validator.constraints.NotEmpty;import com.fasterxml.jackson.annotation.JsonFormat;import com.sm.common.persistence.DataEntity;/** * 工单表Entity * @author 冯文哲 * @version 2018-04-02 */public class WxptWorkorderInfo extends DataEntity{ private static final long serialVersionUID = 1L; private String systemId; // 工单导入或录入时自动生成的系统编号 private String oldSystemId; // 工单导入时原厂家自带的编号 private String uname; // 用户姓名 public WxptWorkorderInfo() { super(); } public WxptWorkorderInfo(String id){ super(id); } @Length(min=0, max=64, message="工单导入时原厂家自带的编号长度必须介于 0 和 64 之间") @com.sm.utils.excel.annotation.ExcelField(title = "服务报告", align = 2, sort = 1) public String getOldSystemId() { return oldSystemId; } public void setOldSystemId(String oldSystemId) { this.oldSystemId = oldSystemId; } @Length(min=0, max=64, message="用户姓名长度必须介于 0 和 64 之间") @NotEmpty(message="用户姓名不能为空") @com.sm.utils.excel.annotation.ExcelField(title = "客户信息", align = 2, sort = 3) public String getUname() { return uname; } public void setUname(String uname) { this.uname = uname; } }
该注解定义:
/** * Copyright © 2012-2016 smkj All rights reserved. */package com.sm.common.utils.excel.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * Excel注解定义 * @author ThinkGem * @version 2013-03-10 */@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface ExcelField { /** * 导出字段名(默认调用当前字段的“get”方法,如指定导出字段为对象,请填写“对象名.对象属性”,例:“area.name”、“office.name”) */ String value() default ""; /** * 导出字段标题(需要添加批注请用“**”分隔,标题**批注,仅对导出模板有效) */ String title(); /** * 字段类型(0:导出导入;1:仅导出;2:仅导入) */ int type() default 0; /** * 导出字段对齐方式(0:自动;1:靠左;2:居中;3:靠右) */ int align() default 0; /** * 导出字段字段排序(升序) */ int sort() default 0; /** * 如果是字典类型,请设置字典的type值 */ String dictType() default ""; /** * 反射类型 */ Class fieldType() default Class.class; /** * 字段归属组(根据分组导出导入) */ int[] groups() default {};}
注解的title即导出的标题。
导入: 首先创造一个map做excel标题和实体类字段映射用,即下面代码的header。
然后调用:
bindToModels方法将excel映射到实体类上,后续代码可自行处理业务逻辑。
/** * 导入数据 * * @param file * @param redirectAttributes * @return */ @RequiresPermissions("wxpt:workorder:wxptWorkorderInfo:edit") @RequestMapping(value = "import", method = RequestMethod.POST) public String importFile(MultipartFile file, RedirectAttributes redirectAttributes) { Mapheader = new HashMap (); header.put("服务报告", "oldSystemId"); header.put("服务类别", "serviceType"); header.put("客户信息", "uname"); header.put("市", "areaCode"); header.put("地址", "uaddress"); header.put("产品信息", "brand"); header.put("附加保修文档号", "fujiabaoxiu"); header.put("维修建议", "weixiujianyi"); header.put("配件建议", "peijianjianyi"); header.put("申请备注", "notesAndInstructions"); header.put("申请时间", "serviceTime"); header.put("技术文档", "jishuwendang"); try { int successNum = 0; int failureNum = 0; StringBuilder failureMsg = new StringBuilder(); ImBeanExcel export = new ImBeanExcel(header); export.init(file); //List list = ei.getDataList(WxptWorkorderInfo.class); List list = export.bindToModels(WxptWorkorderInfo.class, true); if (export.hasError()) { System.out.println(export.getError().toString()); } for (WxptWorkorderInfo workorder : list) { String indent = randomIndentnum.getIndent(); workorder.setSystemId(indent); for (int j = 0; j < workorder.getUname().length(); j++) {//处理导入数据 //拆分用户和电话 if(workorder.getUname().charAt(j)>=48 && workorder.getUname().charAt(j)<=57){ String uname = workorder.getUname().substring(0,j); String uphone = workorder.getUname().substring(j,workorder.getUname().length()); workorder.setUname(uname); workorder.setUphone(uphone.trim()); } } //拆分品牌,产品类别,服务,型号 String [] bra = workorder.getBrand().split("\\s+"); List brandsDict = wxptBrandsDictService.findList(new WxptBrandsDict()); List productDict = wxptProductDictService.findList(new WxptProductDict()); List serviceDict = wxptServiceDictService.findList(new WxptServiceDict()); workorder = ImportDivision.divisionBrand(bra,workorder,brandsDict,productDict,serviceDict); } for (WxptWorkorderInfo workorder : list) { try { if ("true".equals(checkOldSystemId("", workorder.getOldSystemId()))) { BeanValidators.validateWithException(validator, workorder); wxptWorkorderInfoService.save(workorder); successNum++; } else { failureMsg.append(" 工单编号 " + workorder.getOldSystemId() + " 已存在; "); failureNum++; } } catch (ConstraintViolationException ex) { failureMsg.append(" 工单编号 " + workorder.getOldSystemId() + " 导入失败:"); List messageList = BeanValidators.extractPropertyAndMessageAsList(ex, ": "); for (String message : messageList) { failureMsg.append(message + "; "); failureNum++; } } catch (Exception ex) { failureMsg.append(" 工单编号 " + workorder.getOldSystemId() + " 导入失败:" + ex.getMessage()); } } if (failureNum > 0) { failureMsg.insert(0, ",失败 " + failureNum + " 条工单,导入信息如下:"); } addMessage(redirectAttributes, "已成功导入 " + successNum + " 条工单" + failureMsg); } catch (Exception e) { addMessage(redirectAttributes, "导入工单失败!失败信息:" + e.getMessage()); } return "redirect:" + adminPath + "/wxpt/workorder/wxptWorkorderInfo?repage"; }
ImBeanExcel.java工具类:
package com.sm.modules.wxpt.utils.excel;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.lang.reflect.Method;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.ArrayList;import java.util.Date;import java.util.HashMap;import java.util.List;import java.util.Map;import org.apache.log4j.Logger;import org.apache.poi.ss.usermodel.DateUtil;import org.apache.poi.xssf.usermodel.XSSFCell;import org.apache.poi.xssf.usermodel.XSSFDataFormat;import org.apache.poi.xssf.usermodel.XSSFRow;import org.apache.poi.xssf.usermodel.XSSFSheet;import org.apache.poi.xssf.usermodel.XSSFWorkbook;import org.springframework.web.multipart.MultipartFile;/** * 将excel数据装换成数据模型 * * @author * */public class ImBeanExcel { private static final int HEADER = 0; private static final int START = 1; private Logger log = Logger.getLogger(this.getClass()); private XSSFWorkbook book; /** * key:excel对应标题 ,value:对象属性 */ private Mapassociations; /** * 装换失败的数据信息,记录行数 */ private StringBuffer error = new StringBuffer(0); private Map header; /** * 默认的日期格式 */ private String date_format = "yyyy-MM-dd"; private SimpleDateFormat format; /** * 初始化工作簿 * * @param file */ public void init(MultipartFile file) { FileInputStream in; try { in = (FileInputStream) file.getInputStream(); book = new XSSFWorkbook(in); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } public ImBeanExcel(Map associations) { this.associations = associations; format = new SimpleDateFormat(date_format); } public ImBeanExcel(Map associations, String date_format) { this.associations = associations; this.date_format = date_format; format = new SimpleDateFormat(date_format); } /** * * @return true 存在错误,false 不存在错误 */ public boolean hasError() { return error.capacity() > 0; } public StringBuffer getError() { return error; } /** * 获取第一行标题栏数据 * * @param sheet * @return map key:标题栏列下标(0开始) value 标题栏值 */ private void loadHeader(XSSFSheet sheet) { this.header = new HashMap (); XSSFRow row = sheet.getRow(HEADER); int columns = row.getLastCellNum(); for (int i = 0; i < columns; i++) { log.debug("加载标题栏:" + row.getCell(i).getStringCellValue()); String value = row.getCell(i).getStringCellValue(); if (null == value) { throw new RuntimeException("标题栏不能为空!"); } header.put(i, value); } log.debug("<<<<<<<<<<< <标题栏加载完毕> >>>>>>>>>>"); } /** * * @param clazz * @param required * 是否每个属性都是必须的 * @return */ public List 标题栏加载完毕>bindToModels(Class clazz, boolean required) throws Exception { // 获取第一页 XSSFSheet sheet = this.book.getSheetAt(0); int rowNum = sheet.getLastRowNum();// 获取行数 if (rowNum < 1) { return new ArrayList (); } // 加载标题栏数据 this.loadHeader(sheet); List result = new ArrayList (); for (int i = START; i < rowNum; i++) { XSSFRow row = sheet.getRow(i); int cellNum = row.getLastCellNum(); T instance = (T) clazz.newInstance(); for (int columns = 0; columns < cellNum; columns++) { XSSFCell cell = row.getCell(columns); // 判断单元格的数据类型 String value = loadCellType(cell); // 获取单元格的值 if (null == value) { // 如果为必填的则将错误信息记录 if (required) { this.error.append("第" + (i + 1) + "行," + header.get(columns) + "字段,数据为空,跳过!").append("\n"); log.debug("第" + (i + 1) + "行," + header.get(columns) + "字段,数据为空,跳过!"); continue; } }else if("".equals(value.trim())){ // 如果为必填的则将错误信息记录 if (required) { this.error.append("第" + (i + 1) + "行," + header.get(columns) + "字段,数据为空,跳过!").append("\n"); log.debug("第" + (i + 1) + "行," + header.get(columns) + "字段,数据为空,跳过!"); continue; } } else { String key = header.get(columns); // 加载实际值 value = value.replaceAll( "\\\\",""); this.loadValue(clazz, instance, this.associations.get(key), value); } } result.add(instance); } log.debug("<<<< <装换完成" + (this.haserror() ? "有错误信息" : "") ",共有对象:" result.size() "个" "> >>>>>"); return result; } /** * 将单元格数据转换成string类型 * * @param cellType * @param cell * @return */ private String loadCellType(XSSFCell cell) { String value = null; switch (cell.getCellType()) { case XSSFCell.CELL_TYPE_BOOLEAN: value = String.valueOf(cell.getBooleanCellValue()); break; case XSSFCell.CELL_TYPE_NUMERIC: // 判断当前的cell是否为Date if (DateUtil.isCellDateFormatted(cell)) { value = this.formateDate(cell.getDateCellValue()); } else { value = String.valueOf((long) cell.getNumericCellValue()); } break; case XSSFCell.CELL_TYPE_STRING: value = cell.getStringCellValue(); break; case XSSFCell.CELL_TYPE_FORMULA: log.debug("不支持函数!"); break; } return value; } /** * 注入属性值 * * @param instance * @param pro * 属性对象 * @param value * 属性值 */ @SuppressWarnings("unchecked") private void loadValue(Class clazz, T instance, String pro, String value) throws SecurityException, NoSuchMethodException, Exception { String getMethod = this.initGetMethod(pro); Class type = clazz.getDeclaredMethod(getMethod, null).getReturnType(); Method method = clazz.getMethod(this.initSetMethod(pro), type); if (type == String.class) { method.invoke(instance, value); } else if (type == int.class || type == Integer.class) { method.invoke(instance, Integer.parseInt(value)); } else if (type == long.class || type == Long.class) { method.invoke(instance, Long.parseLong(value)); } else if (type == float.class || type == Float.class) { method.invoke(instance, Float.parseFloat(value)); } else if (type == double.class || type == Double.class) { method.invoke(instance, Double.parseDouble(value)); } else if (type == Date.class) { method.invoke(instance, this.parseDate(value)); } } private Date parseDate(String value) throws ParseException { value = value.replaceAll("/", "-"); return format.parse(value); } private String formateDate(Date date) { return format.format(date); } public String initSetMethod(String field) { return "set" + field.substring(0, 1).toUpperCase() + field.substring(1); } public String initGetMethod(String field) { return "get" + field.substring(0, 1).toUpperCase() + field.substring(1); } public String getDate_format() { return date_format; } public void setDate_format(String date_format) { this.date_format = date_format; }} 装换完成">
至此一套完整流程的导入导出基本完成,可复用。